﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MarioArena.World
{
    class PathfindingTile
    {
        public int X { get; set; }
        public int Y { get; set; }
        /// <summary>
        /// Indicates whether this tile is a wall or not.
        /// </summary>
        public bool IsWall { get; set; }
        /// <summary>
        /// Indicates whether this tile is unreachable by jumping or not.
        /// </summary>
        public bool IsUnreachable { get; set; }
        /// <summary>
        /// Indicates whether this tile is eligible for generating a path on or not.
        /// </summary>
        public bool IsEligible { get; set; }

        public bool IsStart { get; set; }
        public bool IsGoal { get; set; }
        public PathfindingTile Parent { get; set; }
        public HashSet<PathfindingTile> Adjacencies { get; set; }

        /// <summary>
        /// Cost of the best known path until the current position.
        /// </summary>
        public int ParentCost { get; set; }
        /// <summary>
        /// Heuristic cost estimate from this tile to the goal.
        /// </summary>
        public int HeuristicCost { get; set; }
        /// <summary>
        /// The estimated total cost of the path, from the start to the goal THROUGH this tile.
        /// </summary>
        public int EstimatedTotalCost { get; set; }

        /// <summary>
        /// Constructs a new Tile.
        /// </summary>
        /// <param name="x">X-position.</param>
        /// <param name="y">Y-position.</param>
        /// <param name="isWall">Whether this tile is a wall or not.</param>
        public PathfindingTile(int x, int y, bool isWall)
        {
            this.X = x;
            this.Y = y;
            this.IsWall = isWall;
            this.Adjacencies = new HashSet<PathfindingTile>();
        }

        /// <summary>
        /// Calculates the adjacent tiles.
        /// </summary>
        public void CalculateAdjacencies()
        {
            int left = X - 1;
            int right = X + 1;
            int top = Y - 1;
            int bottom = Y + 1;

            // NOTE: We only need to check the right and bottom tiles, because the left and top tiles are
            // also set in the process by allocating this tile to the right or bottom tile. Basically this
            if (right < TileEngine.Instance.LevelWidth)
            {                            // rows, columns
                PathfindingTile rightTile = TileEngine.Instance.JumpingGrid[right, Y];
                //if (rightTile.IsWall == false && rightTile.IsUnreachable == false) // Tile to the right is a walkable tile
                if (rightTile.IsEligible) // Tile to the right is a walkable tile
                {
                    if (this.IsEligible) // This tile is walkable
                        rightTile.Adjacencies.Add(this); // Add this tile to the right tile's adjacencies
                                                        // THIS TILE IS THE LEFT TILE OF "rightTile"
                    this.Adjacencies.Add(rightTile); // Add the left tile to this tile's adjacencies
                }
            }

            if (bottom < TileEngine.Instance.LevelHeight)
            {
                PathfindingTile bottomTile = TileEngine.Instance.JumpingGrid[X, bottom];
                //if (bottomTile.IsWall == false && bottomTile.IsUnreachable == false) // Tile at the bottom is a walkable tile
                if (bottomTile.IsEligible) // Tile at the bottom is a walkable tile
                {
                    //if (this.IsEligible && IsReachableFromPlatform()) // This tile is walkable
                    if (this.IsEligible) // This tile is walkable
                        bottomTile.Adjacencies.Add(this); // THIS TILE IS THE TOP TILE OF "bottomTile"
                    this.Adjacencies.Add(bottomTile);
                }
            }
        }

        /// <summary>
        /// Checks whether this tile is reachable by jumping from below, or is too high.
        /// </summary>
        /// <returns></returns>
        private bool IsReachableFromPlatform()
        {
            int topY = this.Y + 1;
            if (topY >= TileEngine.Instance.LevelHeight)
                topY = TileEngine.Instance.LevelHeight - 1;
            int bottomY = this.Y + Settings.JumpHeight; // <JumpHeight> tiles below
            if (bottomY >= TileEngine.Instance.LevelHeight)
                bottomY = TileEngine.Instance.LevelHeight - 1;

            // Check the tiles below
            for (int y = topY; y <= bottomY; y++)
            {
                // If one of the tiles below is a wall, this tile is reachable by jumping from below
                if (TileEngine.Instance.JumpingGrid[this.X, y].IsWall)
                    return true;
            }

            // This tile is unreachable from below
            return false;
        }

        /// <summary>
        /// Gets the estimated total cost of the path, which goes from the start to the 
        /// goal VIA this tile.
        /// </summary>
        /// <param name="goal">The goal tile.</param>
        /// <returns></returns>
        public int GetTotalCost(PathfindingTile goal)
        {
            return GetParentCost() + GetHeuristicCost(goal);
        }

        /// <summary>
        /// Gets the estimated cost to get from this tile to another tile.
        /// </summary>
        /// <param name="goal">The tile to check the cost to.</param>
        /// <returns>The estimated heuristic cost to get from this tile to the specified tile.</returns>
        public int GetHeuristicCost(PathfindingTile goal)
        {
            // Manhattan method
            return Math.Abs(X - goal.X) + Math.Abs(Y - goal.Y);
        }

        /// <summary>
        /// Get the cost of the best known path until the current tile position.
        /// </summary>
        /// <param name="tile">Tile to execute recursion on, getting the cost of the parent. Recursion starts at the current location.</param>
        /// <returns>The cost of the best known path until the current tile position </returns>
        public int GetParentCost()
        {
            if (this.IsStart)
                return 0;

            return 10 + this.Parent.GetParentCost();
        }
    }
}
